package com.winit.core.host.tailer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.file.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by keel2008 on 16/5/24.
 */
public class Tailer4J implements Runnable {

    static final Logger logger = LoggerFactory.getLogger(Tailer4J.class);

//    static final String line_separator = System.getProperties().getProperty("line.separator");
    static final String line_separator = "<br>";

    public Integer watingTime = 1000;

    public String filePath = "";

    public Boolean isClose = Boolean.FALSE;

    public Long filePosition = 0L;

    public Map<String, Listener> listenerMap = new HashMap<String, Listener>();

    public WatchService ws = null;

    public File targetFile = null;

    public RandomAccessFile file = null;

    public Boolean tailStop = Boolean.FALSE;

    public Tailer4J(String filePath, String listenerKey, Listener listener) {
        this.filePath = filePath;

        listenerMap.put(listenerKey, listener);
    }

    public void addListener(String listenerKey, Listener listener) {
        listenerMap.put(listenerKey, listener);
    }

    /**
     * 主方法,运行
     */
    @Override
    public void run() {

        logger.info("Tailer4J启动,filePath="+filePath);

        try {
            //1.初始化文件指针和文件位置.
            this.initFile();

            //2.初始化文件监控器.
            this.registWs();

            //3.读取文件中已存在的内容
//            String text = readFromFile();
//            this.publishMessage(text);

            //4.开始循环,并处理异常.
            while (!tailStop) {

//                    WatchKey watchKey = this.getIfKeepWatching();
                    //3.1 判断是处理,继续,还是退出.
//                    if (watchKey == null) {
//                        continue;
//                    }
                    //3.2 处理的场合,根据文件位置,广播抓取到的内容.

                String msg = readFromFile();

                this.publishMessage(msg);

                Thread.sleep(watingTime);


                    //3.3 更新文件位置.
//                    watchKey.reset();
            }

            logger.info("Tailer4J停止,filePath="+filePath);
        } catch (Exception ex) {
            this.publishMessage("[ERROR]:" + ex.getMessage());
            ex.printStackTrace();
        }finally {
            try {
                this.ws.close();
                this.file.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 按照文件指针位置,从文件尾部读取变化了的文件内容.
     *
     * @return
     * @throws Exception
     */
    private String readFromFile() throws Exception {

        //1.确认文件存在
        assertFileExists();

        //2.确认文件
        long fileLength = targetFile.length();

        if (fileLength == 0) {
            logger.info("fileLength : " + fileLength);
            Thread.sleep(1000);
            return null;
        }

        if (fileLength < this.filePosition && fileLength != 0) {
            this.filePosition = 0L;
        }

        if (fileLength > this.filePosition) {

            file.seek(this.filePosition);

            StringBuilder sb = new StringBuilder();
            String line;

            do {
                line = file.readLine();

                if (line == null) break;

                line = new String(line.getBytes("8859_1"), "UTF-8");

                Pattern pattern = Pattern.compile("^[0-9]*/[0-9]* KB.*");
                Matcher matcher = pattern.matcher(line);
                if( !matcher.matches() ){
                    sb.append(line).append(line_separator);
                }
            } while (line != null);

            this.filePosition = file.getFilePointer();

            if(StringUtils.isEmpty(sb.toString())){
                return null;
            }

            return sb.toString().substring(0,sb.toString().length()-line_separator.length());

        } else {
            return null;
        }
    }


    /**
     * 通过文件变化事件,以及开关制线程是否继续监视
     *
     * @return null代表不继续监视
     * @throws Exception
     */
    private WatchKey getIfKeepWatching() throws Exception {

        assertFileExists();

        if (this.isClose) {
            throw new Exception("Thread has been closed.");
        }

        if (this.listenerMap.size() == 0){
            throw new Exception("Listener all stoped.");
        }

        WatchKey watchKey = ws.poll(this.watingTime, TimeUnit.MILLISECONDS);
        if (watchKey == null) {
            return null;
        }

        List<WatchEvent<?>> watchEvents = watchKey.pollEvents();
        for (WatchEvent<?> event : watchEvents) {
//            //TODO 根据事件类型采取不同的操作。。。。。。。
//            System.out.println("[" + event.context() + "]文件发生了[" + event.kind() + "]事件");
        }

        return watchKey;
    }

    /**
     * 校验文件是否存在.
     *
     * @throws Exception 文件不存在时(比如遭到删除),抛出异常.
     */
    private void assertFileExists() throws Exception {

        Assert.notNull(this.file);
        Assert.notNull(this.targetFile);
        Assert.isTrue(this.targetFile.exists());
        Assert.notNull(this.ws);
    }

    /**
     * 初始化文件对象
     *
     * @throws Exception
     */
    private void initFile() throws Exception {
        this.targetFile = new File(filePath);
        this.file = new RandomAccessFile(targetFile, "r");
//        this.filePosition = targetFile.length();
        this.filePosition = 0L;
    }

    /**
     * 生成一个当前线程使用的文件夹监控器.
     *
     * @throws Exception
     */
    private void registWs() throws Exception {
        WatchService ws = FileSystems.getDefault().newWatchService();

        Path p = Paths.get(this.targetFile.getParentFile().getPath());
        p.register(ws, StandardWatchEventKinds.ENTRY_MODIFY,
                StandardWatchEventKinds.ENTRY_DELETE,
                StandardWatchEventKinds.ENTRY_CREATE);

        this.ws = ws;

    }

    /**
     * 向该Tailer的所有发布者发布消息.
     *
     * @param msg
     */
    public void publishMessage(String msg) {

        if (msg == null || "".equals(msg)) {
            return;
        }

        logger.info("Tailer4J,filePath="+filePath+",msg="+msg);

        for (String key :
                this.listenerMap.keySet()) {
            Listener listener = listenerMap.get(key);
            listener.onMessage(msg,filePath,key);
        }

    }

    /**
     * 去掉一个listener.
     * @param listenerKey
     */
    public void removeListener(String listenerKey) {

        this.listenerMap.remove(listenerKey);
    }

    /**
     * 公开给外部使用的接口定义(只为Tailer服务).
     */
    public interface Listener {

        void onMessage(String msg,String filePath, String listenerKey);

        Boolean isClosed();
    }

//    public static void main(String[] args) {
//
//        Tailer4J jt = new Tailer4J("/Users/wangjian/Desktop/bbb", "test", new Listener() {
//
//            @Override
//            public void onMessage(String msg,String filePath, String listenerKey) {
//                System.out.println("==:"+msg);
//            }
//
//            @Override
//            public Boolean isClosed() {
//                return Boolean.FALSE;
//            }
//        });
//
//        Thread thread = new Thread(jt);
//        thread.start();
//    }


}
